package solver.moves;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import model.EnhancedCostEvaluator;
import model.MachineSelector;
import model.Process;
import model.Solution;
import model.SolverEnvironment;
import solver.BaseExtraLeanIPModelBuilder;
import solver.GrbExtraLeanIPModelBuilder;
import solver.SelectionAgent3;
import solver.SelectionAgent4;

public class MathMove extends AbstractComplexMove {
	
	MachineSelector ms;
	
	public MathMove(SolverEnvironment env, MachineSelector ms) {
		super(env);
		this.ms = ms;
		nop = Math.min(50, getProblem().P);
		noem = Math.min(30, getProblem().M);
	}

	private int nop = 50;
	private int noem = 15;
	private static int init_time = 5;
	private int limitVar=1000;
	private int time_sec = init_time;

	@Override
	public boolean performMove() {
		if(isMathSolverBeenUsed)
			return false;
		else
			isMathSolverBeenUsed=true;
		boolean success = false;
//		EnhancedCostEvaluator ece = env.getFullValidator();
//		ece.computeCost();
//		ece.printNewSolutionCostAnalysis("BEFORE CALL OF MATH SOLVER");
		long init_cost = env.getCostCalculator().getNewSolutionCost();
//		 List<Integer> selectedProcesses =
//		 selectFullRandomProcessesAndMachines(
//		 nop, noem);
		SelectionAgent4 sa4 = new SelectionAgent4(env);
//		List<Integer> selectedProcesses = strategicallySelectProcessesAndMachines(
//				nop, env.getProblem().M/2);
		sa4.setLimitXvars(limitVar);
		sa4.setDependencyConstraintsNeeded(false);
		
		List<Integer> selectedProcesses = sa4.selectFullRandomProcessesAndMachinesFast(40+env.getRandom().nextInt(20),
				10+env.getRandom().nextInt(10), 1);
		if (selectedProcesses.size() == 0)
			selectedProcesses = selectFullRandomProcessesAndMachines(nop, noem);

//		augmentEligibleMachines(selectedProcesses);

		BaseExtraLeanIPModelBuilder modelBuilder = new GrbExtraLeanIPModelBuilder(
				getProblem(), selectedProcesses, false, time_sec, getCc()
						.getNewSolutionCost());
		modelBuilder.setInitialSolution(env.getInitialSolution());
		modelBuilder.setCurrentSolution(env.getCurrentSolution().copy());
		
		// since the number of selected neighborhoods is 1
		modelBuilder.includeDependencyHardConstraints(false);
		
		//Ignore minor costs
//		 modelBuilder.includeServiceMoveCostSoftConstraints(false);
//		 modelBuilder.includeMachineMovedCostSoftConstraints(false);
//		 modelBuilder.includeProcessMoveCostSoftConstraints(false);
		
		
		boolean solved = modelBuilder.solve(true);
		if (solved 
//				&& 
//				((GrbExtraLeanIPModelBuilder)modelBuilder).computeAndGetCostOverIPModel()<
//				env.getCostCalculator().getNewSolutionCost()
				) {
			time_sec = init_time;
//			limitVar+=100;
			System.out.println("MATH MOVE PROBLEM SOLVED");
			long old_cost = env.getCostCalculator().getNewSolutionCost();
			modelBuilder.updateSolution(env, selectedProcesses);
			if(env.getCostCalculator().getNewSolutionCost()>old_cost){
				getInvoker().undoAllCommands();
				env.getCostCalculator().clearDelta();
				getCc().setAndRestoreCostComponentMemory(previousCostState);
			}
			else{
				getInvoker().clearCommands();
				env.getCostCalculator().clearDelta();
				previousCostState = getCc().saveAndGetCostComponentMemory();
				success = true;
			}
//			env.getCostCalculator().printNewSolutionCostAnalysis(
//					"AFTER CALL OF MATH SOLVER");
			// env.setCurrentSolution(sol.copy());
		} else {
			limitVar -= 100;
			time_sec = Math.min(time_sec + 1, 3 * init_time);
			System.out.println("MATH MOVE PROBLEM NOT SOLVED");
			env.getCostCalculator().clearDelta();
			success =  false;
		}
		System.out.println("Math move improvement "+(init_cost-env.getCostCalculator().getNewSolutionCost()));
		isMathSolverBeenUsed = false;
		return success;
	}

	private void augmentEligibleMachines(List<Integer> selectedProcesses) {
		Set<Integer> machineUnion = new HashSet<Integer>();
		for (Integer p : selectedProcesses) {
			machineUnion.addAll(getProblem().getEligibleMachinesFor(p));
		}
		List<Integer> machines = new ArrayList<Integer>(machineUnion);
		for (Integer p : selectedProcesses) {
			Collections.shuffle(machines, getRandom());
			List<Integer> ep = getProblem().getEligibleMachinesFor(p);
			int c = 0;
			while (ep.size() < noem && c < machines.size()) {
				if (!ep.contains(machines.get(c)))
					ep.add(machines.get(c));
				c++;
			}
		}
	}

	private List<Integer> strategicallySelectProcessesAndMachines(int nop,
			int noem) {
		SelectionAgent3 sa3 = new SelectionAgent3(env);
		getProblem().clearEligibleMachines();
		nop = nop < getProblem().P ? nop : getProblem().P;
		noem = noem < getProblem().M ? noem : getProblem().M;
		List<Integer> selectedProcesses = null;
		int what = getRandom().nextInt(4);
		switch (what) {
		case 0:
			sa3.selectProcessesAndMachinesBasedOnOneProcessMove(nop, noem);
			selectedProcesses = sa3.getSelectedProcesses();
			break;
		case 1:
			sa3.selectProcessesAndMachinesBasedOnTwoProcessesExchangeMove(nop,
					noem);
			selectedProcesses = sa3.getSelectedProcesses();
			break;
		case 2:
			sa3.selectProcessesAndMachinesBasedOnTwoProcessesExchangeMove(nop,
					noem);
			selectedProcesses = sa3.getSelectedProcesses();
			break;
		case 3:
			sa3.selectProcessesAndMachinesBucket();
			selectedProcesses = sa3.getSelectedProcesses();
			break;
		}
		System.out.println(selectedProcesses);
		return selectedProcesses;
	}

	// duplicate code with BaseSolver, should be removed
	public List<Integer> selectFullRandomProcessesAndMachines(int nop, int noem) {
		getProblem().clearEligibleMachines();
		nop = nop < getProblem().P ? nop : getProblem().P;
		noem = noem < getProblem().M ? noem : getProblem().M;
		List<Integer> selectedProcesses = new ArrayList<Integer>();

		List<Integer> ballot_processes = new ArrayList<Integer>();
		for (int p = 0; p < getProblem().P; p++) {
			ballot_processes.add(p);
		}
		Collections.shuffle(ballot_processes, getRandom());
		for (int i = 0; i < nop; i++) {
			selectedProcesses.add(ballot_processes.get(i));
		}
		List<Integer> ballot_machines = new ArrayList<Integer>();
		for (int m = 0; m < getProblem().M; m++) {
			ballot_machines.add(m);
		}
		randomlySelectMachinesForProcesses(selectedProcesses, ballot_machines,
				noem);
		// clearConflictConstraints(selectedProcesses);
		return selectedProcesses;
	}

	// duplicate code with BaseSolver, should be removed
	public void randomlySelectMachinesForProcesses(
			List<Integer> selectedProcesses, List<Integer> ballot_machines,
			int noem) {
		if (ballot_machines.size() < noem)
			noem = ballot_machines.size();
		for (Integer p_id : selectedProcesses) {
			Process proc = getProblem().getProcesses().get(p_id);
			proc.clearEligibleMachines();
			int current_machine_id = getCurrent_solution()
					.getMachineIdForProcessId(p_id);
			int initial_machine_id = getInitial_solution()
					.getMachineIdForProcessId(p_id);
			proc.addEligibleMachine(current_machine_id);
			proc.addEligibleMachine(initial_machine_id);
			Collections.shuffle(ballot_machines, getRandom());
			for_loop: for (Integer candidate_machine_id : ballot_machines) {
				proc.addEligibleMachine(candidate_machine_id);
				if (proc.getEligibleMachines().size() == noem - 1)
					break for_loop;
			}
		}
	}

	// @Override
	// public boolean performMove() {
	// MathSolverGrbLC solver = new MathSolverGrbLC(problem,
	// iv.getInitial_solution(), iv.getNew_solution().copy(),
	// 10+random.nextInt(nop), 10+random.nextInt(noem));
	// long cost_before = cc.getNewSolutionCost();
	// solver.solveWithTimeLimit(time_sec, false);
	// solver.getCostEvaluator().computeCost();
	// assert (cost_before >= solver.getCostEvaluator().getNewSolutionCost());
	// if(cost_before >= solver.getCostEvaluator().getNewSolutionCost()){
	// System.out.println("cost_before:"+cost_before+" -> cost_after:"+solver.getCostEvaluator().getNewSolutionCost());
	// setCurrentSolution(solver.getCurrentSolution());
	// return true;
	// }
	// else
	// return false;
	// }

	public void undo() {

	}

}
